Skip to content

Quality pass: green baseline, tests, content validator (+42 content fixes), perf & a11y#3

Merged
NeoVand merged 16 commits into
mainfrom
improvement-pass-1
Jun 19, 2026
Merged

Quality pass: green baseline, tests, content validator (+42 content fixes), perf & a11y#3
NeoVand merged 16 commits into
mainfrom
improvement-pass-1

Conversation

@NeoVand

@NeoVand NeoVand commented Jun 19, 2026

Copy link
Copy Markdown
Owner

A multi-phase quality pass. Every change is an isolated, verified commit; the working tree builds clean and all gates are green.

Status — all green

  • npm run lint (was 604 errors) → 0
  • npm run check (was 3 errors / 5 warnings) → 0 / 0
  • npm run validate (new) → passes (18 advisory warnings, see below)
  • 43 unit tests (Vitest) + 17 e2e tests (Playwright) pass
  • Production build succeeds; verified live in-browser (graph, detail panels, TCP/TLS simulations, tooltips, mermaid diagrams, layout switching, reduced-motion) with zero console errors.

Note: this PR is stacked on graph-subcategory-tier-and-tuning, so the diff also includes those 2 prior feature commits.

Highlights

Content integrity (the big one)

A new build-time cross-reference validator (scripts/validate-cross-references.ts, gating build + CI) resolves every structured id reference and every inline [[link]] / {{concept}} in prose. It immediately caught 42 real content bugs — refs rendering as dead text / inert tooltips — all fixed:

  • Added 9 genuinely-missing concepts (congestion-collapse, ossification, TCP handshake, EGP, HTTP verbs, Comet, phishing, EAP-TTLS, RSN).
  • Fixed systematic slips: protocol ids wrapped as {{concepts}}, [[ipv4]][[ip]], STUN/ICE/TURN→nat-traversal, a pioneer's non-existent "security" category, etc.

Green baseline + safety net

  • Fixed all type errors and the 604 lint errors; removed 537 unnecessary string escapes via a position-driven codemod (a naive regex would have corrupted template literals — verified byte-identical compiled output).
  • Test infrastructure from ~zero: 43 unit (parsers/math/colors/engine) + 17 e2e (every critical path), plus CI gating lint/check/validate/unit/e2e.

Perf, correctness, a11y, hygiene

  • highlight.js (~138 KB gz) now lazy-loads on demand instead of on every first paint.
  • syncPositions matches simulation nodes by id, not array index (TDD).
  • Global prefers-reduced-motion reset across all entrance animations.
  • Deleted unused 1.9 MB screenshot.png; shrank og-image.png 1.9 MB → 544 KB.
  • Centralized mermaid init into one loadMermaid() helper; hardened stripRichTextMarkup.

Needs your editorial input (deliberately not auto-changed)

  1. 18 duplicate concept ids — two authored definitions share an id, one silently shadows the other (e.g. sip-invite, ns-record, bgp-update, apple/google/microsoft/meta/cloudflare). Surfaced as validator warnings rather than me deleting your writing. npm run validate lists them.
  2. 3 pioneers referenced but not in the registry (Jay Kreps; Andy Stanford-Clark; Arlen Nipper) — unwrapped to bold text rather than fabricate birth years.

Deliberately deferred (higher-risk, better watched)

Protocol metadata/content split for first paint; GenericLink/ModalShell component dedup; canvas render-loop micro-opts; sitemap (needs a canonical deploy URL). All documented in IMPROVEMENT_PLAN.md and docs/baselines/bundle-baseline.md.

🤖 Generated with Claude Code

claude and others added 16 commits June 19, 2026 01:59
- SequencePlayer: guard definition in bindStepsToDom; type SVG elements as
  SVGGraphicsElement so getBBox() resolves.
- Introduce shared StoryContent base interface; CategoryStory and
  SubcategoryStory extend it. CategoryStoryView accepts StoryContent so
  subcategory stories render without a type mismatch.
- StoryDiagramModal: containerEl is now $state and captured locally in the
  async render closure.
- SimulatorView: derive config from protocolId and load via $effect.
- ConceptTrigger: fold hover styling into enter/leave; add focus/blur so
  keyboard users get the concept tooltip.
- CategoryIcon: drive hover animation via CSS :hover (+ reduced-motion guard)
  instead of a JS handler on a roleless span.

npm run check: 0 errors, 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Genuine code fixes:
- Remove dead code: FOUNDATION_TEASERS/totalParts/foundationSections and an
  unused navigateToBookChapter import (DetailPanel); unused stubChapters helper
  (book/chapters); unused imports/vars in SearchBar, OutageView, NodeTooltip,
  SequencePlayer, layouts; vestigial gqlRequestLayer params.
- Drop the unused `color` prop from JourneyListView (it colors per-journey) and
  the matching caller arg; drop unused `stepIndex` prop from ActorStage + caller.
- Replace empty `interface MeshLink {}` with a type alias.
- Add keys to name-segment {#each} blocks (ProtocolHeader, MobileDetailSheet).
- Type the dev-only window globals (__dev/__tourDriver) in app.d.ts, removing
  three `as any` casts; make tourDriver const.
- Remove three unused eslint-disable directives in scripts/.

Rule-level config (with justifications in eslint.config.js):
- Off: svelte/no-navigation-without-resolve (app centralizes ${base} prefixing)
  and svelte/prefer-svelte-reactivity (all Map/Set are ephemeral locals).
- Allow _-prefixed unused identifiers.

Per-site disables with justification for intentional patterns:
- svelte/no-dom-manipulating at mermaid innerHTML injection sites.
- svelte/no-at-html-tags for trusted highlighted code + static SVG icons.

npm run check: 0/0. eslint: only no-useless-escape remains (handled next).
Build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…en codemod

Adds scripts/fix-useless-escapes.ts, which consumes ESLint's JSON report and
deletes only the exact backslashes ESLint flagged as unnecessary (right-to-left
per line). A naive regex would have been wrong — e.g. in `Berkeley Unix\'s
\`.rhosts\``, the necessary `\'` (single-quoted string) must stay while the
redundant backtick escapes go; the codemod preserves exactly that distinction.

Verification:
- ESLint no-useless-escape: 537 → 0; 0 skipped (every position was a backslash).
- 34/35 changed files: esbuild-canonicalized output byte-identical → string
  values provably unchanged.
- text-parser.ts: two regex char-class simplifications ([\[ → [ inside classes),
  proven behaviorally identical across 23 edge-case inputs.
- npm run check: 0/0. Build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repo-wide `prettier --write` over source (Svelte/TS/JS/CSS/JSON). Pure
formatting — no code changed; check/lint/build all green afterward.

Also extends .prettierignore to exclude long-form prose working docs
(research/, podcast-blueprints/, *.md notes) where prettier's markdown
reformatting is non-idempotent and adds no value; those files are left exactly
as authored so the CI `prettier --check` gate stays meaningful for code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New ci.yml runs prettier/eslint (npm run lint), svelte-check (npm run check),
  and a production build on pull requests and non-main pushes.
- deploy.yml now runs the same lint + check gates before building, so main
  cannot deploy a red baseline.

(Test + cross-reference-validation gates are added in the test/validator
commits that follow.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The earlier reactivity-warning fix wrapped simState.load() in an $effect, which
made Svelte flag unsafe state mutation at runtime (config/userValues written
inside an effect). The parent already remounts via {#key protocolId}, so the
load is genuinely once-per-mount; untrack(() => getSimulation(protocolId))
expresses that, silences the state_referenced_locally warning, and removes the
runtime errors. Verified clean in-browser across TCP/TLS simulate + stepping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unit (vitest, node env): 39 tests over the pure logic the content layer leans
on — sequence-parser (notes/arrows/blocks), text-parser (rich-markup grammar,
cross-ref resolution, bold-group recursion, strip), math, and color helpers.
Config added to vite.config.ts; scripts test:unit/test:unit:watch added; `npm
test` now runs unit then e2e.

E2e (playwright, against the production preview — no window.__dev): 15 tests
covering graph load + console-clean, /p/tcp detail + overview, Simulate tab,
404, category/subcategory, search, every registry/book/journey page prerender,
and a mobile bottom-sheet + no-horizontal-overflow check.

Deleted the demo scaffolding routes (src/routes/demo/**) and their placeholder
spec. CI (ci.yml) now runs test:unit + e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…refs

Adds scripts/validate-cross-references.ts, wired into `npm run build` and CI.
It loads every registry and resolves all cross-references — structured
(protocol connections, categoryId, subcategory membership, journey steps,
comparison pairs, RFC/outage/pioneer/frontier protocol lists, story parents)
and inline [[id|label]] / {{concept}} markup in prose — failing the build on the
first broken link. Exports categoryStories / subcategoryStories / allPairs so it
can see those registries.

It immediately found 42 real bugs (refs that rendered as dead text / inert
tooltips); all fixed:
- Added 9 missing concepts: egp, congestion-collapse, ossification,
  tcp-handshake, http-verbs, comet, phishing, ttls, rsn.
- Corrected misnamed refs: {{as}}→autonomous-system, {{ct}}→certificate-
  transparency, [[head-of-line-blocking]]→{{…}} (it's a concept).
- Fixed protocol ids wrapped as concepts: {{ip|quic|websockets|mptcp}} → [[…]].
- [[ipv4]]→[[ip]]; [[ice|stun|turn]]→[[nat-traversal|…]]; [[datagram-transport]]
  (a subcategory, not a protocol) → plain text.
- Unwrapped 3 not-yet-added pioneers (Jay Kreps; Stanford-Clark; Nipper) to bold
  text rather than fabricate birth-year data — flagged to add later.
- pioneer dan-bernstein categories 'security' (not a category) → 'utilities'.

check/lint/unit/build all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeExample imported highlight.js core + 8 grammars statically (~150 KB raw /
~138 KB gz), so every first paint shipped it even when no code example was on
screen. Switched to a dynamic import() on mount: code renders as escaped plain
text until hljs resolves, then highlights reactively.

Measured on the prerendered / (single build, since chunk hashes are
non-deterministic): hljs grammar code is no longer in the initial chunk set;
initial JS is ~1171 KB gz across 23 chunks. mermaid and driver.js were already
dynamic and confirmed absent from initial. The remaining initial weight is
hand-authored prose — documented in docs/baselines as the next (riskier) win.

Added an e2e guard that http1's code example still highlights after the lazy
load. check/lint/16 e2e all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- static/screenshot.png (1.9 MB) was not referenced anywhere — removed.
- og-image.png was 2678×1612 / 1.9 MB, far larger than a social card needs.
  Resized to the standard 1200px width (1200×722) → 544 KB, a 71% cut. Format
  unchanged (crisp text, no JPEG artifacts) and no og:image dimensions are
  declared, so no meta changes needed.

Note: a spec-valid sitemap.xml needs absolute URLs, but no deploy domain
(CNAME/og:url) is configured in the repo — deferred until the canonical URL is
known rather than guessing the GitHub Pages path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Added a global reduced-motion reset in layout.css that collapses animations,
transitions, and smooth scroll to near-instant when the OS requests it. This
covers the detail-panel slide-in plus the modal / tooltip / simulator-arrow
entrance animations that previously ignored the preference — in one place
rather than per component. The canvas graph already honors the same preference
in JS.

Added an e2e test asserting reduced-motion users still receive the detail-panel
content (animation off must not mean content hidden). check/lint/17 e2e green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
syncPositions copied simNodes[i] → nodes[i], which only works while d3-force's
internal array stays in creation order. A reordered or resized `nodes` array
would silently scatter every node to the wrong position. Now keyed by id.

Added simulation.test.ts (TDD): a reordered-array case that fails on the old
index logic and passes on the id match, plus an orphan-node case. Verified
in-browser that the graph still settles (live node positions populated, console
clean). 41 unit tests green.

Note: the render-loop perf micro-opts from the audit (memoize per-frame
node/connection maps, evict settled hover/dim animations, pool gradients,
delta-time viewport lerp) are deferred — they touch the canvas hot path with no
current user-facing bug, so they want a watched, profiled pass rather than this
autonomous run. Tracked in IMPROVEMENT_PLAN Phase 7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The four diagram components (SequencePlayer, MermaidDiagram, StoryDiagramModal,
StoryDiagram) each repeated the dynamic import('mermaid') + initialize() with
the same base config (theme/security/font), differing only in their sequence/
flowchart tuning. Extracted loadMermaid(overrides) into mermaid-helpers so the
shared base lives in one place; each component passes only its own overrides.
Behavior unchanged — verified TCP sequence diagram + transport category-story
diagrams still render, console clean, build green.

The larger GenericLink (6 inline link components) and ModalShell (3 modals)
dedups from the audit are deferred: they carry visual-regression risk that
wants a watched, screenshot-diffed pass rather than this autonomous run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stripRichTextMarkup feeds plain-text surfaces (NodeTooltip body, AccessibleGraph
screen-reader text). A label-less ref like [[tcp]] resolved to '' there, silently
dropping the word. It now falls back to the display label (protocol abbreviation,
concept term, RFC number, …), mirroring how parseRichText renders the same ref —
so stripped text matches rendered text.

Currently latent (no bare refs sit in oneLiner/description fields today, and the
cross-ref validator can't catch it since the ref is valid) — this hardens the
path for future content. Added unit tests; 43 unit tests green, validate green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cross-ref validator now flags duplicate ids. Protocol/pioneer/RFC/outage/
frontier collisions are fatal (none exist today, so this is a guard). Concepts
have 18 pre-existing collisions where two authored definitions share an id and
one silently shadows the other in conceptMap — reported as warnings for
editorial review rather than auto-deleting authored content or unilaterally
picking a "winner" (e.g. sip-invite, ns-record, bgp-update, and the company
entries apple/google/microsoft/meta/cloudflare each have two definitions).

Surfacing > guessing: the visible definition is valid today, so this is a
content-curation task for the owner, now tracked by the build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NeoVand NeoVand merged commit f3848d4 into main Jun 19, 2026
2 checks passed
@NeoVand NeoVand deleted the improvement-pass-1 branch June 19, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants